const Dragify = (function () {
  const Watcher = function () {
    this.events = {};
  };
  Watcher.prototype = {
    construct: Watcher,
    on: function (type, fn) {
      this._getEventInfo(type).push(fn);
      return this;
    },
    trigger: function (type) {
      const event = this._getEventInfo(type);
      const params = Array.prototype.slice.call(arguments, 1);
      event.forEach(function (fn) {
        fn.apply(fn, params);
      });
      return this;
    },
    _getEventInfo: function (type) {
      if (!this.events[type]) this.events[type] = [];
      return this.events[type];
    },
    remove: function (type, fn) {
      const event = this._getEventInfo(type);
      if (!fn) {
        this.events[type] = [];
      } else {
        event.splice(event.indexOf(fn), 1);
      }
      return this;
    },
  };

  
  function drag(t) {
    this.$elem = t;
    this.watcher = new Watcher();
    this.init();
  }
  drag.isTouch = function (e) {
    return (
      "ontouchstart" in window ||
      (window.DocumentTouch && document instanceof window.DocumentTouch) ||
      navigator.maxTouchPoints > 0 ||
      window.navigator.msMaxTouchPoints > 0
    );
  };
  const EVENTS = drag.isTouch()
    ? ["touchstart", "touchmove", "touchend"]
    : ["mousedown", "mousemove", "mouseup"];
  drag.prototype = {
    constructor: drag,
    init: function () {
      const that = this;
      that.$elem.addEventListener(EVENTS[0], function (ev) {
        const target = that._getEventInfo(ev);
        const offsetParent = that.$elem.offsetParent;
        const clientWidth = document.documentElement.clientWidth;
        const clientHeight = document.documentElement.clientHeight;
        const offsetWidth = that.$elem.offsetWidth;
        const offsetHeight = that.$elem.offsetHeight;
        const transition = getComputedStyle(that.$elem)["transition"];
        const zIndex = getComputedStyle(that.$elem).zIndex;

        const diffX = target.clientX - that.$elem.offsetLeft;
        const diffY = target.clientY - that.$elem.offsetTop;
        const pDiffX = offsetParent.offsetLeft || 0;
        const pDiffY = offsetParent.offsetTop || 0;

        that.watcher.trigger("start", that.$elem);
        
        document.addEventListener(EVENTS[1], move);
        document.addEventListener(EVENTS[2], up);

        function move(ev) {
          const evMoveTarget = that._getEventInfo(ev);
          let left = evMoveTarget.clientX - diffX;
          let top = evMoveTarget.clientY - diffY;

          if (left + pDiffX < 0) {
            left = left - (left + pDiffX);
          }
          if (top + pDiffY < 0) {
            top = top - (top + pDiffY);
          }
          if (left + pDiffX + offsetWidth > clientWidth) {
            left = clientWidth - (pDiffX + offsetWidth);
          } 
          if (top + pDiffY + offsetHeight > clientHeight) {
            top = clientHeight - (pDiffY + offsetHeight);
          }
          that.$elem.style.position = "absolute";
          that.$elem.style["transition"] = "unset";
          that.$elem.style.left = left + "px";
          that.$elem.style.top = top + "px";
          that.$elem.style.zIndex = 999999;
          that.watcher.trigger("move", that.$elem);
        }
        function up() {
          document.removeEventListener(EVENTS[1], move);
          document.removeEventListener(EVENTS[2], up);
          that.$elem.style["transition"] = transition;
          that.$elem.style.zIndex = zIndex;
          that.watcher.trigger("end", that.$elem);
        }
       
      });
    },
    on: function () {
      this.watcher.on.apply(this.watcher, arguments);
      return this.watcher;
    },
    _getEventInfo: function (ev) {
      return drag.isTouch() ? ev.targetTouches[0] : ev;
    },
  };
  

  return drag;
})();
